// Renderer.cpp
//
#include "Renderer.h"

#include "OpenGL.h"
#include "Box.h"
#include "Color.h"
#include "FloatingOrigin.h"
#include "Font.h"
#include "TextRenderer.h"

#include <iostream>

#include "MemCheck.h"
using namespace OpenGL;

// ------------------------------------------------------------------------------------------------
Renderer::Renderer()
: _useGradient(false),
  _zoomFactor(1.0),
  _textRenderer(this),
  fonts(NULL)
{
	updateViewBox();
}

// ------------------------------------------------------------------------------------------------
Renderer::~Renderer()
{}

// ------------------------------------------------------------------------------------------------
void Renderer::begin2D()
{
	// Open a 2D screen
	// [0,0] is the lower left corner
	//Math::dvec3 c = convertWorldToWindow( Math::dvec3(0),false );
	glPushMatrix();

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glViewport(_viewport.x,_viewport.y,_viewport.z,_viewport.w);
	glOrtho(_viewport.x,_viewport.z,_viewport.y,_viewport.w,-1.0,1.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	//glTranslatef( c.x,c.y,0 );
	//glScalef( _zoomFactor,_zoomFactor,1 );
}

// ------------------------------------------------------------------------------------------------
void Renderer::end2D()
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

// ------------------------------------------------------------------------------------------------
void Renderer::setFont( Font *f )
{
	fonts = f;
}

// ------------------------------------------------------------------------------------------------
void Renderer::setCenter( const Math::dvec3 &c )
{	
	Math::dvec3 newCenter(c);
	if( !_sceneBox.empty() )
	{
		updateViewBox();

		if( _viewBox.minPoint().x + newCenter.x < _sceneBox.minPoint().x )	
			newCenter.x = _sceneBox.minPoint().x + _viewBox.sizes().x*0.5;
		if( _viewBox.maxPoint().x + newCenter.x > _sceneBox.maxPoint().x )	
			newCenter.x = _sceneBox.maxPoint().x - _viewBox.sizes().x*0.5;
		if( _viewBox.minPoint().y + newCenter.y < _sceneBox.minPoint().y )	
			newCenter.y = _sceneBox.minPoint().y + _viewBox.sizes().y*0.5;
		if( _viewBox.maxPoint().y + newCenter.y > _sceneBox.maxPoint().y )	
			newCenter.y = _sceneBox.maxPoint().y - _viewBox.sizes().y*0.5;

		if( _viewBox.sizes().x > _sceneBox.sizes().x )
			newCenter.x = _sceneBox.center().x;
		if( _viewBox.sizes().y > _sceneBox.sizes().y )
			newCenter.y = _sceneBox.center().y;
	}

	//std::cout << newCenter.x << "," << newCenter.y << std::endl;

	_center = newCenter;
}

// ------------------------------------------------------------------------------------------------
void Renderer::setClearColor(const Color &gradientTop, const Color &gradientBottom )
{
	bkTop        = gradientTop;
	bkBottom     = gradientBottom;
	_useGradient = true;
}

// ------------------------------------------------------------------------------------------------
void Renderer::setClearColor(const Color &flatColor )
{
	bkTop        = flatColor;
	bkBottom     = flatColor;
	_useGradient = false;
}

// ------------------------------------------------------------------------------------------------
void Renderer::clearBackground()
{
	glClearColor( bkTop.r(),bkTop.g(),bkTop.b(),bkTop.a() );
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	if( _useGradient )
	{
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(-1,1, -1,1, -1,1);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glDisable(GL_DEPTH_TEST);
		glBegin(GL_TRIANGLE_STRIP); // Z
			// Top
			glColor4fv( bkTop.rgba() );
			glVertex2i(-1,+1);
			glVertex2i(+1,+1);

			// Bottom
			glColor4fv( bkBottom.rgba() );
			glVertex2i(-1,-1);
			glVertex2i(+1,-1);
		glEnd();
		glPopAttrib();	
	}
}

// ------------------------------------------------------------------------------------------------
void Renderer::zoomInOut( double factor, const Math::dvec3 &target )
{
	if( Math::abs(factor) < 1e-8 )
		factor = 1.0;

	_zoomFactor *= factor;

	setCenter( (_center - target)/Math::dvec3(factor) + target ); //compute the new center position

	updateViewBox();
}

// ------------------------------------------------------------------------------------------------
void Renderer::setZoom( double zoomValue )
{
	if( zoomValue > 0.0 )
		_zoomFactor = zoomValue;

	updateViewBox();

	//std::cout << zoomFactor << std::endl;
}

// -------------------------------------------------------------------------------------------------
void Renderer::fitView( const Math::box3 &b )
{
	Math::box3 box(b);

	// center in 0,0,0 if no box
	if( box.empty() )
	{
		if( !_sceneBox.empty() )
			box = _sceneBox;
		else
		{
			_zoomFactor = 1.0;
			setCenter( Math::dvec3(0) );
			updateViewBox();
			_origin.set(_center);
			glGetDoublev( GL_MODELVIEW_MATRIX, (double*)&(_modelViewMatrix)[0] );
			return;
		}
	}

	// center in box.minpoint if box is too small
	if( box.diag() < 1e-8 )
	{
		_zoomFactor = 1.0;
		setCenter( box.minPoint() );
		updateViewBox();
		_origin.set(_center);
		glGetDoublev( GL_MODELVIEW_MATRIX, (double*)&(_modelViewMatrix)[0] );
		return;
	}

	// resize only if the desired box is big enough
	const double bw = box.sizes().x;
	const double bh = box.sizes().y;

	// height * bw < width * bh
	if( _viewport.w * bw < _viewport.z * bh )	_zoomFactor = 2. * _viewport.w / ( (double)_viewport.z * bh );// scale by height
	else										_zoomFactor = 2. / bw;										 // scale by width

	setCenter( box.center() );
	updateViewBox();
	_origin.set( _center );
	glGetDoublev( GL_MODELVIEW_MATRIX, (double*)&(_modelViewMatrix)[0] );
}

// ------------------------------------------------------------------------------------------------
void Renderer::enableFloatingOrigin( bool on )
{
	_origin.enable(on);
}

// ------------------------------------------------------------------------------------------------
void Renderer::setViewport(int x, int y, int w, int h )
{
	_viewport.x = x;
	_viewport.y = y;
	_viewport.z = w;
	_viewport.w = h;
	
	glViewport( _viewport.x, _viewport.y, _viewport.z, _viewport.w );
	updateViewBox();
}

// ------------------------------------------------------------------------------------------------
void Renderer::setViewMatrix( const Math::dvec3 &eye,
							  const Math::dvec3 &target,
							  const Math::dvec3 &up )
{
	_modelViewMatrix = Math::lookAt(eye,target,up);
	
	glPushAttrib( GL_TRANSFORM_BIT );
	glMatrixMode(GL_MODELVIEW);
	glLoadMatrixd( (const double*)&(_modelViewMatrix)[0] );
	glPopAttrib();

	// Centering
	_origin.set( _center );

	glGetDoublev( GL_MODELVIEW_MATRIX, (double*)&(_modelViewMatrix)[0] );
}

// ------------------------------------------------------------------------------------------------
void Renderer::updateViewBox()
{
	const double aspect = _viewport.z / (double)_viewport.w;
	const double a      = 1.0 / _zoomFactor;
	const double b      = (a / aspect);

	_viewBox = Math::box3( Math::dvec3(-a,-b,-50.0), Math::dvec3(a,b,50.0) ); // for frustum culling!
}


// ------------------------------------------------------------------------------------------------
void Renderer::setProjectionMatrix()
{
	//updateViewBox();

	_projectionMatrix = Math::ortho(_viewBox.minPoint().x,
									_viewBox.maxPoint().x,
									_viewBox.minPoint().y,
									_viewBox.maxPoint().y,
									_viewBox.minPoint().z,
									_viewBox.maxPoint().z);

	glPushAttrib( GL_TRANSFORM_BIT );
	glMatrixMode( GL_PROJECTION );
	glLoadMatrixd( (const double*)&(_projectionMatrix)[0] );
	glPopAttrib();
}

// ------------------------------------------------------------------------------------------------
void Renderer::setRTCMatrix( const Math::dvec3 &center )
{
	glGetDoublev(GL_MODELVIEW_MATRIX, (double*)&(_modelViewMatrix)[0]);

	//Math::dvec4 originEye = _modelViewMatrix * Math::dvec4(origin.get(),1.0);
	//Math::dvec4 centerEye = _modelViewMatrix * Math::dvec4(center,1.0);
	//
	//Math::dmat4 mv( Math::column(_modelViewMatrix,0),
	//				Math::column(_modelViewMatrix,1),
	//				Math::column(_modelViewMatrix,2),
	//				Math::dvec4(Math::make_vec3(&(centerEye-originEye)[0]),1.0) );

	Math::dvec4 centerEye = _modelViewMatrix * Math::dvec4(center-_origin.get(),1.0);
	
	Math::dmat4 mv( Math::column(_modelViewMatrix,0),
					Math::column(_modelViewMatrix,1),
					Math::column(_modelViewMatrix,2),
					centerEye );
	
	glLoadMatrixd( Math::value_ptr(mv) );

	//OpenGL::Math::dvec3 origin      ( renderer->getOrigin());
	//OpenGL::Math::dvec3 t = curveCenter - origin;
	//glTranslated( t.x, t.y, t.z );
}

// ------------------------------------------------------------------------------------------------
void Renderer::storeModelView()
{
	glGetDoublev(GL_MODELVIEW_MATRIX, (double*)&(_modelViewMatrix)[0]);
}

// ------------------------------------------------------------------------------------------------
void Renderer::setSceneBox( const Math::box3 &b )
{
	_sceneBox = b;
}

// ------------------------------------------------------------------------------------------------
void Renderer::drawText()
{
	if( !fonts || _textRenderer.isEmpty() )
		return;

	_textRenderer.update();

	begin2D();
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
	_textRenderer.draw();
	end2D();
}


// -------------------------------------------------------------------------------------
void Renderer::addText( const Math::dvec3 &pos, const std::string &text, const Color &color, double size, TextRenderer::TextAlignment alignment )
{
	_textRenderer.addTag( TextTag(text,pos,color,size,0.0,alignment) );
}

// -------------------------------------------------------------------------------------
//Convert a point from world coords in a window pixel 
Math::dvec3 Renderer::convertWorldToWindow(const Math::dvec3 &p, bool flipY)
{
	Math::dvec3 q      = p - _origin.get();
	Math::dvec3 winPos = Math::project( q, _modelViewMatrix, _projectionMatrix, _viewport );

	if( flipY )
		winPos.y = _viewport.w - winPos.y - 1;
	
	winPos.z = 0;
	
	return winPos;
}

// -------------------------------------------------------------------------------------
Math::dvec3 Renderer::convertWorldToWindow(const Math::dvec2 &p, bool flipY)
{
	Math::dvec3 q      = Math::dvec3(p,0) - _origin.get();
	Math::dvec3 winPos = Math::project( q, _modelViewMatrix, _projectionMatrix, _viewport );

	if( flipY )
		winPos.y = _viewport.w - winPos.y - 1;
	
	winPos.z = 0;
	
	return winPos;
}

// ------------------------------------------------------------------------------------------------
Math::box3 Renderer::convertWorldToWindow(const Math::box3 &b, bool flipY )
{
	Math::dvec3 minP = b.minPoint() - _origin.get();
	Math::dvec3 maxP = b.maxPoint() - _origin.get();

	Math::dvec3 p0 = Math::project( minP, _modelViewMatrix, _projectionMatrix, _viewport );
	Math::dvec3 p1 = Math::project( maxP, _modelViewMatrix, _projectionMatrix, _viewport );

	if( flipY )
	{
		p0.y = _viewport.w - p0.y - 1;
		p1.y = _viewport.w - p1.y - 1;
	}
	
	p0.z = 0;
	p1.z = 0;

	return Math::box3( p0,p1 );
}

// -------------------------------------------------------------------------------------
Math::dvec3 Renderer::convertWindowTo3D(int x, int y, bool flipY)
{
	Math::dvec3 t(x,y,0);

	if( flipY )
		t.y = _viewport.w - y - 1;

	Math::dvec3 out = Math::unProject( t, _modelViewMatrix, _projectionMatrix, _viewport );
	
	return out+_origin.get();
}

// -------------------------------------------------------------------------------------------------
Math::dvec2 Renderer::convertWindowTo2D(int x, int y, bool flipY)
{
	Math::dvec3 t(x,y,0);

	if( flipY )
		t.y = _viewport.w - y - 1;

	Math::dvec3 out = Math::unProject( t, _modelViewMatrix, _projectionMatrix, _viewport );
	
	out+=_origin.get();

	return Math::dvec2(out.x,out.y);
}

// ------------------------------------------------------------------------------------------------
bool Renderer::isVisible( const Math::dvec3 &p ) const
{
	return _viewBox.translated(_center).contains(p);
}

// ------------------------------------------------------------------------------------------------
bool Renderer::isVisible( const Math::box3  &b ) const
{
	return _viewBox.translated(_center).overlaps(b);
}

// ------------------------------------------------------------------------------------------------
double Renderer::worldSize() const
{
	//double d = viewBox.diag();
	return 2.0 * _zoomFactor;
	//return 1.0/viewBox.diag();
}

// ------------------------------------------------------------------------------------------------
double Renderer::pixelToWorldSize( float pixels ) const
{
	//drawPrecision = 4. / (zoomFactor * width()) * .5;

	return ((double)pixels) * 1.0/(_zoomFactor * ((double)_viewport.z)); // tested and working
}

// -------------------------------------------------------------------------------------------------
int Renderer::worldSizeToPixels(double distance) const
{
	Math::dvec3 t(0,distance,0);

	Math::dvec3 out = Math::unProject( t, _modelViewMatrix, _projectionMatrix, _viewport );

	return (int)Math::length(t);
}

// ------------------------------------------------------------------------------------------------
void Renderer::openSelectionMode(int x, int y)
{
	_selection.openSelectionMode( Math::ivec2(x,y), _projectionMatrix, _viewport );
}

// ------------------------------------------------------------------------------------------------
void Renderer::closeSelectionMode()
{
	_selection.closeSelectionMode();
}

// ------------------------------------------------------------------------------------------------
bool Renderer::selectionFailed() const
{
	return _selection.failed();
}

// ------------------------------------------------------------------------------------------------
std::vector<unsigned int> Renderer::getSelectionNames( Selection::SelectionMode mode )
{
	std::vector<unsigned int> v;

	switch( mode )
	{
		case Selection::Nearest:
			{
			unsigned int id;
			if(  _selection.getNearestObject(id) )
				v.push_back(id);
			}
			break;

		case Selection::All:
			v = _selection.getObjects();
			break;
	}

	 return v;
}



// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void Renderer::glVertex2( const Math::vec2 &v ) { glVertex2dv( Math::value_ptr(Math::dvec3(v,0)-_origin.get()) ); }
void Renderer::glVertex3( const Math::vec3 &v ) { glVertex3dv( Math::value_ptr(Math::dvec3(v)-_origin.get()) );   }
void Renderer::glVertex2( const Math::dvec2 &v ){ glVertex2dv( Math::value_ptr(Math::dvec3(v,0)-_origin.get()) ); }
void Renderer::glVertex3( const Math::dvec3 &v ){ glVertex3dv( Math::value_ptr(v-_origin.get()) );                }
void Renderer::glVertex2( const Math::ivec2 &v ){ glVertex2dv( Math::value_ptr(Math::dvec3(v,0)-_origin.get()) ); }
void Renderer::glVertex3( const Math::ivec3 &v ){ glVertex3dv( Math::value_ptr(Math::dvec3(v)-_origin.get()) );   }

// ------------------------------------------------------------------------------------------------
//void Renderer::setModelMatrix( const Math::mat4 &m )
//{
//	mMatrix = m;
//	glLoadMatrixd( (const double*)(&(mMatrix)[0]) );
//}
//
//// ------------------------------------------------------------------------------------------------
//void Renderer::resetModelMatrix()
//{
//	mMatrix = Math::mat4(1.f);
//	glLoadIdentity();
//}
//
//// ------------------------------------------------------------------------------------------------
//void Renderer::translateModelMatrix( const Math::vec3 &t )
//{
//	mMatrix = Math::translate(mMatrix,t);
//	glLoadMatrixf( (const float*)&(vMatrix*mMatrix)[0] );
//}
//
//// ------------------------------------------------------------------------------------------------
//void Renderer::scaleModelMatrix( const Math::vec3 &s )
//{
//	mMatrix = Math::scale( mMatrix, s );
//	glLoadMatrixf( (const float*)&(vMatrix*mMatrix)[0] );
//}
//
//// ------------------------------------------------------------------------------------------------
//void Renderer::rotateModelMatrix( double angle, const Math::vec3 &axis)
//{
//	mMatrix = Math::rotate( mMatrix, (float)angle, axis );
//	glLoadMatrixf( (const float*)&(vMatrix*mMatrix)[0] );
//}
